#include "CPU.h"
#include "base/Simulator.h"
#include "base/signals/SigManager.h"
#include "base/syscalls.h"

#include <sys/mman.h>
#include <syscall.h>
#include <errno.h>
#include <sys/time.h>
#include <unistd.h>

#ifdef __linux__
#define u32 uint32_t
#include <linux/futex.h>
#endif

#ifdef _AIX
#include <fcntl.h>
#include <sys/procfs.h>
int syscall_number (char* syscall_name) {
    pid_t pid = getpid();

    const int filename_length = 4096;
    char filename[filename_length];
    snprintf(filename, filename_length, "/proc/%d/sysent", pid);

    int fd = openOrDie(filename);

    int bytes = sizeof(prsysent_t)-sizeof(prsyscall_t);
    prsysent_t prsysent;
    int n = read(fd, &prsysent, bytes);
    ASSERT(n == bytes);

    uint32_t nsyscalls = prsysent.pr_nsyscalls;

    prsyscall_t* syscalls = (prsyscall_t*)calloc(nsyscalls, sizeof(prsyscall_t));
    ASSERT(syscalls != NULL);

    uint32_t i;
    for (i = 0; i < nsyscalls; i++) {
        prsyscall_t prsyscall;
        int n = read(fd, &prsyscall, sizeof(prsyscall_t));
        ASSERT(n == sizeof(prsyscall_t));
        uint32_t pr_number  = prsyscall.pr_number;
        uint32_t pr_nameoff = prsyscall.pr_nameoff;
        syscalls[i].pr_number  = pr_number;
        syscalls[i].pr_nameoff = pr_nameoff;
    }

    for (i = 0; i < nsyscalls; i++) {
        uint32_t pr_number  = syscalls[i].pr_number;
        uint32_t pr_nameoff = syscalls[i].pr_nameoff;
        off_t off = lseek(fd, pr_nameoff, SEEK_SET);
        ASSERT((uint32_t)off == pr_nameoff);

        char name[1024];
        read(fd, name, 1024);
        if (strcmp(name, syscall_name) == 0) {
            return pr_number;
        }
    }
    Log->e("could not find syscall %s", syscall_name);
    return -1;
}
#else // TODO config options HAS_SYSCALL_H
#include <sys/syscall.h>
#endif

static int SYS_execve_number;
static int SYS_exit_number;
static int SYS_fork_number;
static int SYS_gettimeofday_number;
static int SYS_mmap_number;
static int SYS_munmap_number;
static int SYS_brk_number;
static int SYS_sigaction_number;
static int SYS_sigreturn_number;
static int SYS_vfork_number;
static int SYS_write_number;
#ifdef  __linux__
static int SYS_futex_number;
static int SYS_exit_group_number;
static int SYS_clone_number;
static int SYS_mmap2_number;
static int SYS_rt_sigaction_number;
#endif

static SysCallPreHookPtr pre_sc_hook;
static SysCallPostHookPtr post_sc_hook;

Logger* syscallLog;
static bool syscallLogOpened = false;

void init_syscalls () {

    // get numbers
#ifdef _AIX
    SYS_execve_number       = syscall_number("execve");
    SYS_exit_number         = syscall_number("exit");
    SYS_fork_number         = syscall_number("fork");
    SYS_gettimeofday_number = syscall_number("gettimeofday");
    SYS_mmap_number         = syscall_number("mmap");
    SYS_munmap_number       = syscall_number("munmap");
    SYS_brk_number          = syscall_number("brk");
    SYS_sigaction_number    = syscall_number("sigaction");
    SYS_sigreturn_number    = syscall_number("sigreturn");
    SYS_vfork_number        = syscall_number("vfork");
    SYS_write_number        = syscall_number("write");
#else // !_AIX
    SYS_execve_number       = SYS_execve;
    SYS_exit_number         = SYS_exit;
    SYS_fork_number         = SYS_fork;
    SYS_gettimeofday_number = SYS_gettimeofday;
    SYS_mmap_number         = SYS_mmap;
    SYS_munmap_number       = SYS_munmap;
    SYS_brk_number          = SYS_brk;
    SYS_sigaction_number    = SYS_sigaction;
    SYS_sigreturn_number    = SYS_sigreturn;
    SYS_vfork_number        = SYS_vfork;
    SYS_write_number        = SYS_write;
#ifdef __linux__
    SYS_futex_number        = SYS_futex;
    SYS_exit_group_number   = SYS_exit_group;
    SYS_clone_number        = SYS_clone;
    SYS_mmap2_number        = SYS_mmap2;
    SYS_rt_sigaction_number = SYS_rt_sigaction;
#endif // !__linux__
#endif // !_AIX

    pre_sc_hook = NULL;
    post_sc_hook = NULL;

    ASSERT(!syscallLogOpened);
    syscallLog = NEW Logger("syscallLog", "sc", "-:w,log.sim", "system call tracing");
    syscallLogOpened = true;
}

void register_pre_syscall_hook (SysCallPreHookPtr f) {
    pre_sc_hook = f;
}

void register_post_syscall_hook (SysCallPostHookPtr f) {
    post_sc_hook = f;
}

#ifdef __linux__
/* 
   glibc defines a "struct sigaction" which is not the same as the
   linux kernel definition. glibc calls the kernel version "struct
   kernel_sigaction". glibc translates between these and our syscall
   code will see the kernel_sigaction version. However, this isn't a
   typically installed header. valgrind defines a "struct
   vki_sigaction" for similar reasons in one of its headers, but it
   seems overkill to depend on valgrind headers to be installed.
*/
struct k_sigaction {
    __sighandler_t  sa_handler1;
    unsigned long   sa_flags;
    void          (*sa_restorer)(void);
    sigset_t        sa_mask;
};
void kernel_sigaction_to_libc_sigaction (struct k_sigaction* kernel, 
                                         struct sigaction*   libc)
{
    libc->sa_handler  = kernel->sa_handler1;
    libc->sa_flags    = kernel->sa_flags;
    libc->sa_restorer = kernel->sa_restorer;
    memcpy(&libc->sa_mask, &kernel->sa_mask, sizeof(sigset_t));
}
void libc_sigaction_to_kernel_sigaction(struct sigaction*   libc,
                                        struct k_sigaction* kernel)
{
    kernel->sa_handler1 = libc->sa_handler;
    kernel->sa_flags    = libc->sa_flags;
    kernel->sa_restorer = libc->sa_restorer;
    memcpy(&kernel->sa_mask, &libc->sa_mask, sizeof(sigset_t));
}
                                      
#else // !__linux__
#define k_sigaction sigaction
#define kernel_sigaction_to_libc_sigaction(kernel,libc)
#define libc_sigaction_to_kernel_sigaction(libc,kernel)
#endif // !__linux__

SysCallHookRet default_pre_handle_syscall (CPU* cpu,
                                           Reg_s sc_id,
                                           Reg* args,
                                           Reg* sc_return)
{
    // handle exit
    if (sc_id == SYS_exit_number ||
#ifdef __linux__
        sc_id == SYS_exit_group_number ||
#endif
        false)
    {
        static bool quiesced = false;

        // use syscall quiesce machinery first, before we
        // Processor::halt which calls rawFlushCaches.
        if (!quiesced) {
            global_sim->quiesce(cpu->P);
            quiesced = true;
        }
        if (!global_sim->isQuiesced()) {
            return SC_STALL;
        }
        quiesced = false;

        // halt so that Simulator::stop can scan for rogue processors
        cpu->P->halt();
        
        // global rawFlushCaches needs to happen while we are quiesced
        global_sim->rawFlushCaches(0,0);

        // has to happen after Simulator::rawFlushCaches and Processor::halt
        global_sim->unquiesce(cpu->P);


        if (args) {
            global_sim->stop(args[0]);
        } else {
            global_sim->stop(0);
        }

        syscallLog->d("exit syscall hit by CPU %d at T=%d\n",
                      cpu->getID(), 
                      (int)global_sim->getSimclock());
        delete syscallLog;
        
        return SC_HANDLED; // to get in committed instruction trace
    }

    // check for unsupported calls
    if (sc_id == SYS_execve_number ||
        sc_id == SYS_fork_number   ||
        sc_id == SYS_vfork_number  ||
#ifdef __linux__
        sc_id == SYS_clone_number  ||
#endif // __linux__
        false)
    {
        // we don't support this. stop it.
        Log->e("Unsupported syscall %d on CPU %d at T=%d\n",
               (int)sc_id,
               cpu->getID(),
               (int)global_sim->getSimclock());
        return SC_HANDLED; // to get in itrace
    }

    // sigaction
    if (sc_id == SYS_sigaction_number ||
#ifdef __linux__
        sc_id == SYS_rt_sigaction_number ||
#endif // __linux__
        false) {


        // extract sigaction arguments
        int signum          = (int)args[0];
        VA  kernelActPtr    = ADDR_UINT32(args[1]); // kernel sigaction
        VA  kernelOldActPtr = ADDR_UINT32(args[2]); // kernel sigaction

        // if actPtr argument is non-null, extract data from caches to local copy
        struct k_sigaction kernelAct;
        struct sigaction         act;
        struct sigaction*        actPtr;
        if (kernelActPtr != 0) {
            cpu->P->getMemory(kernelActPtr, sizeof(struct k_sigaction), (uint8*)&kernelAct);
            kernel_sigaction_to_libc_sigaction(&kernelAct, &act);
            actPtr = &act;
        }
        else {
            actPtr = NULL;
        }

        // if oldact argument is non-null, invalidate cache since we will be overwriting the data
        struct sigaction  oldAct;
        struct sigaction* oldActPtr;
        if (kernelOldActPtr != 0) {
            // flush first to make sure we don't lose return address
            // when we invalidate memory, which is likely to be stack
            // allocated...
            global_sim->rawFlushCaches(                                kernelOldActPtr, sizeof(struct k_sigaction));
            global_sim->invalidateCaches(cpu->P, "struct k_sigaction", kernelOldActPtr, sizeof(struct k_sigaction));
            oldActPtr = &oldAct;
        }
        else {
            oldActPtr = NULL;
        }

        global_sim->getSM()->registerHandler(signum, actPtr, oldActPtr);

        if (oldActPtr != NULL) {
            ASSERT(kernelOldActPtr != 0);
            libc_sigaction_to_kernel_sigaction(&oldAct, (struct k_sigaction*)ADDR_VOIDSTAR(kernelOldActPtr));
        }

        signalLog->i("[syscalls] Simulated sigaction(%d, ", signum);
        if (actPtr == NULL) {
            signalLog->i("%p, ", actPtr);
        }
        else {
            signalLog->i("%p={sa_handler=%p, sa_flags=%x, sa_restorer=%p, sa_mask=%lx}, ",
                         actPtr, 
                         actPtr->sa_handler,
                         actPtr->sa_flags,
                         actPtr->sa_restorer,
                         actPtr->sa_mask.__val[0]);
        }
        if (oldActPtr == NULL) {
            signalLog->i("%p)\n", oldActPtr);
        }
        else {
            signalLog->i("%p={sa_handler=%p, sa_flags=%x, sa_restorer=%p, sa_mask=%lx})\n",
                         oldActPtr, 
                         oldActPtr->sa_handler,
                         oldActPtr->sa_flags,
                         oldActPtr->sa_restorer,
                         oldActPtr->sa_mask.__val[0]);
        }

        *sc_return = 0;                                    
        return SC_HANDLED;
    }

    // sigreturn
#ifdef __APPLE__
    // This is for the BSD style sigreturn for user, not the internal Linux
    // one of the same name
    if (sc_id == SYS_sigreturn_number) {
        ASSERT(false);
        signalLog->i("[syscalls] SYS_sigreturn_number\n");
        cpu->P->getSigHandler()->sigreturn((ucontext_t*)ADDR_VOIDSTAR(args[0]));
        // We return SC_SCALL here because sigreturn has change the PC under
        // the hood.  So we aren't really stalling, we just don't want
        // something to advance the PC for us.
        return SC_STALL;
    }
#endif // __APPLE__

    // futex
#ifdef __linux__
    if (sc_id == SYS_futex_number) {
        // note that we don't do flush for this.
        // we just ignore this silently.
        signalLog->i("%d:%lld:%.16llX [syscalls] skipping syscall futex(%p, %d, %d, %p, %p, %d).\n",
                     cpu->getID(), 
                     global_sim->getSimclock(), 
                     cpu->getPC(), 
                     (int*)ADDR_UINT32STAR(args[0]),
                     (int)args[1],
                     (int)args[2],
                     (const struct timespec*)ADDR_UINT32STAR(args[3]),
                     (int*)ADDR_UINT32STAR(args[4]),
                     (int)args[5]);

        switch (args[1]) {
        case FUTEX_WAIT: {
            *sc_return = 0;
            break;
        }
        case FUTEX_WAKE: {
            *sc_return = 0;
            break;
        }
        default: {
            ASSERT(false);
        }
        }            
        return SC_HANDLED;
    }
#endif

    if (sc_id == SYS_mmap_number) {
        void*  start  = ADDR_UINT32STAR(args[0]);
        size_t length =                 args[1];
        int    prot   =                 args[2];
        int    flags  =                 args[3];
        int    fd     =                 args[4];
        off_t  offset =                 args[5];
        
        syscallLog->d("%d:%lld:%.16llX [syscalls] mmap(%0x, %d, %d, %d, %d, %d) => ",
                      cpu->getID(),
                      global_sim->getSimclock(),
                      cpu->getPC(),
                      (int)start,
                      length,
                      prot,
                      flags,
                      fd,
                      (int)offset);
        *sc_return = ADDR_UINT64(mmap(start, length, prot, flags, fd, offset));
        syscallLog->d("%x\n", ADDR_UINT32(*sc_return));
        return SC_HANDLED;
    }

    if (sc_id == SYS_mmap2_number) {
        void*  start  = ADDR_UINT32STAR(args[0]);
        size_t length =                 args[1];
        int    prot   =                 args[2];
        int    flags  =                 args[3];
        int    fd     =                 args[4];
        off_t  offset =                 args[5];
        
        syscallLog->d("%d:%lld:%.16llX [syscalls] mmap2(%0x, %d, %d, %d, %d, %d) => ",
                      cpu->getID(),
                      global_sim->getSimclock(),
                      cpu->getPC(),
                      (int)start,
                      length,
                      prot,
                      flags,
                      fd,
                      (int)offset);
        *sc_return = ADDR_UINT64(mmap(start, length, prot, flags, fd, offset*4096));
        syscallLog->d("%x\n", ADDR_UINT32(*sc_return));
        return SC_HANDLED;
    }

    if (sc_id == SYS_munmap_number) {
        void*  start  = ADDR_UINT32STAR(args[0]);
        size_t length =                 args[1];
        syscallLog->d("%d:%lld:%.16llX [syscalls] munmap(%0x, %0x) => ",
                      cpu->getID(),
                      global_sim->getSimclock(),
                      cpu->getPC(),
                      (int)start,
                      length);
        global_sim->invalidateCaches(cpu->P, "munmap", ADDR_UINT64(start), length);
        *sc_return = munmap(start, length);
        syscallLog->d("%x\n", ADDR_UINT32(*sc_return));
        return SC_HANDLED;
    }

    if (sc_id == SYS_brk_number) {
        void* end_data_segment = ADDR_UINT32STAR(args[0]);
        syscallLog->d("%d:%lld:%.16llX [syscalls] brk(%0x) => ",
                      cpu->getID(),
                      global_sim->getSimclock(),
                      cpu->getPC(),
                      (int)end_data_segment);
#ifdef __linux__
        // linux brk(2) returns current end of segment on error input such as NULL.
        // this is not the behavior of typical libc brk function
        if (end_data_segment == NULL) {
            *sc_return = ADDR_UINT64(global_sim->getEndDataSegment());
        }
        else {
#endif
            void* current_end_data_segment = global_sim->getEndDataSegment();
            if (ADDR_UINT64(end_data_segment) < ADDR_UINT64(current_end_data_segment)) {
                global_sim->invalidateCaches(cpu->P, 
                                             "brk", 
                                             ADDR_UINT64(end_data_segment), 
                                             (ADDR_UINT64(current_end_data_segment) - 
                                              ADDR_UINT64(end_data_segment)));
            
            }
            global_sim->setEndDataSegment(end_data_segment);
            *sc_return = brk(end_data_segment);
#ifdef __linux__
            *sc_return = ADDR_UINT64(global_sim->getEndDataSegment());
        }
#endif
        syscallLog->d("%x\n", ADDR_UINT32(*sc_return));
        return SC_HANDLED;
    }

    if (sc_id == SYS_gettimeofday_number) {
        VA tvPtr = ADDR_UINT32(args[0]);
        VA tzPtr = ADDR_UINT32(args[1]);

        // if output pointer arguments are non-null, invalidate cache since we will be overwriting the data
        if (tvPtr != 0) {
            // flush first to make sure we don't lose return address
            // when we invalidate memory, which is likely to be stack
            // allocated...
            global_sim->rawFlushCaches(                            tvPtr, sizeof(struct timeval));
            global_sim->invalidateCaches(cpu->P, "struct timeval", tvPtr, sizeof(struct timeval));
        }
        if (tzPtr != 0) {
            // flush first to make sure we don't lose return address
            // when we invalidate memory, which is likely to be stack
            // allocated...
            global_sim->rawFlushCaches(                             tzPtr, sizeof(struct timezone));
            global_sim->invalidateCaches(cpu->P, "struct timezone", tzPtr, sizeof(struct timezone));
        }
        int result = gettimeofday((struct timeval*) ADDR_VOIDSTAR(tvPtr), 
                                  (struct timezone*)ADDR_VOIDSTAR(tzPtr));
                                  
        syscallLog->d("%d:%lld:%.16llX [syscalls] gettimeofday(%p, %p) => %d\n",
                      cpu->getID(),
                      global_sim->getSimclock(),
                      cpu->getPC(),
                      ADDR_VOIDSTAR(tvPtr),
                      ADDR_VOIDSTAR(tzPtr),
                      result);
        *sc_return = result;
        return SC_HANDLED;
    }

    if (sc_id == SYS_write_number) {
        int    fd     =        (int)args[0];
        VA     bufPtr = ADDR_UINT32(args[1]);
        size_t count  =        (int)args[2];

        uint8* buf = (uint8*)CALLOC(count, sizeof(uint8));
        ASSERT(buf != NULL);

        // extract data from caches to local copy
        cpu->P->getMemory(bufPtr, count, buf);

        int result = write(fd, buf, count);
        FREE(buf);
                                  
        syscallLog->d("%d:%lld:%.16llX [syscalls] write(%d, %p, %d) => %d\n",
                      cpu->getID(),
                      global_sim->getSimclock(),
                      cpu->getPC(),
                      fd,
                      ADDR_VOIDSTAR(bufPtr),
                      count,
                      result);
        *sc_return = result;
        return SC_HANDLED;
    }
    return SC_NOTHANDLED;
}

SysCallHookRet default_post_handle_syscall (CPU* cpu, Reg_s sc_id, Reg* args) {
    // handle exit
    if (sc_id == SYS_exit_number ||
#ifdef __linux__
        sc_id == SYS_exit_group_number ||
#endif
        false)
    {
        return SC_HANDLED;
    }

    // check for unsupported calls
    if (sc_id == SYS_execve_number ||
        sc_id == SYS_fork_number   ||
        sc_id == SYS_vfork_number  ||
#ifdef __linux__
        sc_id == SYS_clone_number  ||
#endif // __linux__
             false)
    {
        ASSERT(false);
    }

    // sigaction
    if (sc_id == SYS_sigaction_number ||
#ifdef __linux__
        sc_id == SYS_rt_sigaction_number ||
#endif // __linux__
        false) {
        return SC_HANDLED;
    }

    // sigreturn
    if (sc_id == SYS_sigreturn_number) {
        // BSD-like OS's such as Mac OS X require the sigreturn
        // syscall to be called by the users handler. We used to
        // implement this in an older version of the simulator. In
        // linux, the sigreturn syscall is typically what is called
        // when a user handler returns, which we have implemented with
        // ISA_EXT_SIGEXIT. We could potentially use the syscall
        // instead of ISA_EXT_SIGEXIT
        ASSERT(false);
    }

    // futex
    #ifdef __linux__
    if (sc_id == SYS_futex_number) {
        return SC_HANDLED;
    }
    #endif
    if (sc_id == SYS_mmap_number) {
        return SC_HANDLED;
    }
    if (sc_id == SYS_mmap2_number) {
        return SC_HANDLED;
    }
    if (sc_id == SYS_munmap_number) {
        return SC_HANDLED;
    }
    if (sc_id == SYS_brk_number) {
        return SC_HANDLED;
    }
    if (sc_id == SYS_gettimeofday_number) {
        return SC_HANDLED;
    }
    if (sc_id == SYS_write_number) {
        return SC_HANDLED;
    }
 
    return SC_NOTHANDLED;
}

SysCallHookRet do_pre_syscall_hook (CPU* cpu,
                                    Reg_s sc_id,
                                    Reg* args,
                                    Reg* sc_return)
{   
    // running default syscall handling b/c if we know how to handle them,
    // we dont want to do whatever the user thinks he should do (like flush
    // caches, etc.)
    //
    SysCallHookRet res = default_pre_handle_syscall(cpu, sc_id, args, sc_return);
    syscallLog->d("%i:%lli: default_pre_handle_syscall (sc_id: %lld) => %d\n",
                  cpu->P->getID(),
                  global_sim->getSimclock(),
                  sc_id,
                  res);

    if (pre_sc_hook && res == SC_NOTHANDLED) {
        res = (*pre_sc_hook)(cpu, sc_id, args, sc_return);
    }

    return res;
}

void do_post_syscall_hook (CPU* cpu, Reg_s sc_id, Reg* args) {
    SysCallHookRet res = default_post_handle_syscall(cpu, sc_id, args);

    if (post_sc_hook && res == SC_NOTHANDLED) {
        res = (*post_sc_hook)(cpu, sc_id, args);
    }

    if (global_sim->hasSyscallLock(cpu->P)) {
        global_sim->releaseSyscallLock(cpu->P);
        syscallLog->d("%i:%lli:do_post_syscall_hook: unlock (%p)\n",
                      cpu->P->getID(),
                      global_sim->getSimclock(),
                      cpu->P);
    }

    syscallLog->d("%i:%lli: post-syscall (sc_id: %lld)\n",
                  cpu->P->getID(),
                  global_sim->getSimclock(),
                  sc_id);

    // cant stall...you must move on...
    ASSERT(res != SC_STALL);
}

SysCallHookRet checkSyscallInParallelRegion (CPU* cpu,
                                             Reg_s sc_id,
                                             Reg* args,
                                             Reg* sc_return)
{
    
    int runningProcs = global_sim->getNumRunningCpus();
    if (runningProcs > 1) {
        if (cpu->P->firstCycle) {
            Log->w("%d:%lld:%llx syscall %d in parallel region\n",
                   cpu->P->getID(),
                   global_sim->getSimclock(),
                   cpu->getPC(),
                   (int)sc_id);
        }
    }
    return SC_NOTHANDLED;
}

SysCallHookRet std_pre_syscall_hook (CPU* cpu,
                                     Reg_s sc_id,
                                     Reg* args,
                                     Reg* sc_return)
{
    if (!global_sim->hasSyscallLock(cpu->P) &&
        !global_sim->acquireSyscallLock(cpu->P))
    {
        if (cpu->P->isQuiescing()) {
            quiesceLog->d("%d is about to quiesce.\n", cpu->P->getID());
            cpu->P->sendQuiesceRequest();
        }
        return SC_STALL;
    }

    //
    // This is what happens:
    //
    // j) quiesce, so that there are no ld/st/evicts
    // 2) flush dirty data from caches
    //

    static bool quiesceSent = false;

    if (!quiesceSent) {
        global_sim->quiesce(cpu->P);
        quiesceSent = true;
    }

    if (!global_sim->isQuiesced()) {
        return SC_STALL;
    }

    // this is instantaneous
    global_sim->rawFlushCaches(0, 0);

    quiesceSent = false;
    return SC_NOTHANDLED;
}

SysCallHookRet std_post_syscall_hook (CPU* cpu, Reg_s sc_id, Reg* args) {
    //
    // 1) update the caches
    // 2) unquiesce
    //

    global_sim->updateCaches();
    global_sim->unquiesce(cpu->P);

    return SC_NOTHANDLED;
}
